Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update in app expiry notifications over time #6851

Merged

Conversation

kl
Copy link
Contributor

@kl kl commented Sep 24, 2024

Changes the in app account expiry notifications to a flow that emits at the starts of the notification threshold (currently 3 days before expiry), and then emits every time the notification update duration is passed (currently 1 day).


This change is Reviewable

@kl kl added the Android Issues related to Android label Sep 24, 2024
Copy link

linear bot commented Sep 24, 2024

@kl kl force-pushed the ensure-we-update-inappaccountexpiry-notification-droid-1348 branch 11 times, most recently from 0db709e to 6d843c6 Compare September 30, 2024 20:04
Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 16 of 16 files at r2, all commit messages.
Reviewable status: all files reviewed, 5 unresolved discussions (waiting on @kl)


android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt line 26 at r2 (raw file):

fun Period.isNegative() =
    normalizedStandard().let {

Would any of these work instead?

    normalizedStandard().toStandardSeconds() < 0

or

    normalizedStandard().toStandardDuration().millis < 0

android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 14 at r2 (raw file):

    tickStart: Duration,
    updateInterval: (expiry: DateTime) -> Duration,
): Flow<Period> = flow {

Since I'm a bit senile, remind me, why did we say Period instead of Duration?


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 41 at r2 (raw file):

    var delayCount = calculateDelaysNeeded(expiry.millisFromNow(), currentUpdateInterval)

    while (delayCount > 0) {

I haven't fully thought this through, but it feels like it should work to make use of the do {} while loop https://kotlinlang.org/docs/control-flow.html#while-loops

    do {
        // emit()
        // newUpdate interval
        // calulate new delay
        // evaluate hasAnotherEmission
    } while(hasAnotherEmission)
    emit(Period.ZERO)

Then we should be able to remove these lines:

   // Always emit at start of flow.
    emit(Period(DateTime.now(), expiry))

    // Delay until the next update interval.
    delay(expiry.millisFromNow() % currentUpdateInterval)

    var delayCount = calculateDelaysNeeded(expiry.millisFromNow(), currentUpdateInterval)

whilst making the code more readable.


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 64 at r2 (raw file):

// Note that the returned delays may add upp to less than the remaining time, for example
// if we have 100ms remaining and currentUpdateIntervalMillis is 40ms this function will return 2.
private fun calculateDelaysNeeded(millisUntilExpiry: Long, currentUpdateIntervalMillis: Long): Int {

Shouldn't be a problem but we should consider to return Long since we are diving with Long to avoid any overflow.


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 68 at r2 (raw file):

        0
    } else {
        (millisUntilExpiry / currentUpdateIntervalMillis).toInt()

This is floorDiv I believe.

Copy link
Contributor

@Pururun Pururun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed all commit messages.
Reviewable status: all files reviewed, 6 unresolved discussions (waiting on @kl)


android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt line 57 at r2 (raw file):

    @Test
    fun `initial state should be empty`() = runTest {
        // Arrange, Act, Assert

This comment is kind of useless, so I think it should be either removed into test { } or removed.

@kl kl force-pushed the ensure-we-update-inappaccountexpiry-notification-droid-1348 branch from 6d843c6 to de93214 Compare October 3, 2024 11:54
Copy link
Contributor Author

@kl kl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 7 of 16 files reviewed, 6 unresolved discussions (waiting on @Pururun and @Rawa)


android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt line 26 at r2 (raw file):

Previously, Rawa (David Göransson) wrote…

Would any of these work instead?

    normalizedStandard().toStandardSeconds() < 0

or

    normalizedStandard().toStandardDuration().millis < 0

No but we can do this instead fun Period.isNegative() = toStandardDuration().millis < 0


android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt line 57 at r2 (raw file):

Previously, Pururun (Jonatan Rhodin) wrote…

This comment is kind of useless, so I think it should be either removed into test { } or removed.

Done.


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 14 at r2 (raw file):

Previously, Rawa (David Göransson) wrote…

Since I'm a bit senile, remind me, why did we say Period instead of Duration?

Period is used here

fun Resources.getExpiryQuantityString(accountExpiry: Period): String {
    return if (accountExpiry.isNegative() || accountExpiry == Period.ZERO) {
        getString(R.string.out_of_time)
    } else if (accountExpiry.years > 0) {
        getRemainingText(this, R.plurals.years_left, accountExpiry.years)
    } else if (accountExpiry.months >= 3) {
        getRemainingText(this, R.plurals.months_left, accountExpiry.months)
    } else if (accountExpiry.months > 0 || accountExpiry.days >= 1) {
        getRemainingText(this, R.plurals.days_left, accountExpiry.days)
    } else {
        getString(R.string.less_than_a_day_left)
    }
}

because Duration doesn't have methods to convert to years or months


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 68 at r2 (raw file):

Previously, Rawa (David Göransson) wrote…

This is floorDiv I believe.

Looks like floorDiv can return negative numbers, but we can do this instead:

private fun calculateDelaysNeeded(millisUntilExpiry: Long, currentUpdateIntervalMillis: Long): Long =
    millisUntilExpiry.coerceAtLeast(0) / currentUpdateIntervalMillis

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 9 of 9 files at r3, all commit messages.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on @albin-mullvad, @kl, and @Pururun)


android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt line 26 at r2 (raw file):

Previously, kl (Kalle Lindström) wrote…

No but we can do this instead fun Period.isNegative() = toStandardDuration().millis < 0

🙌 Very nice!


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 14 at r2 (raw file):

Previously, kl (Kalle Lindström) wrote…

Period is used here

fun Resources.getExpiryQuantityString(accountExpiry: Period): String {
    return if (accountExpiry.isNegative() || accountExpiry == Period.ZERO) {
        getString(R.string.out_of_time)
    } else if (accountExpiry.years > 0) {
        getRemainingText(this, R.plurals.years_left, accountExpiry.years)
    } else if (accountExpiry.months >= 3) {
        getRemainingText(this, R.plurals.months_left, accountExpiry.months)
    } else if (accountExpiry.months > 0 || accountExpiry.days >= 1) {
        getRemainingText(this, R.plurals.days_left, accountExpiry.days)
    } else {
        getString(R.string.less_than_a_day_left)
    }
}

because Duration doesn't have methods to convert to years or months

Ah, yes. no toStandardMonth() or toStandardYear(). Kind of sucks, I guess we could add it ourselves as extension functions but it would be a bit less accurate. I'm fine with Period. @Pururun @albin-mullvad do you have any opinion about this?


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 41 at r2 (raw file):

Previously, Rawa (David Göransson) wrote…

I haven't fully thought this through, but it feels like it should work to make use of the do {} while loop https://kotlinlang.org/docs/control-flow.html#while-loops

    do {
        // emit()
        // newUpdate interval
        // calulate new delay
        // evaluate hasAnotherEmission
    } while(hasAnotherEmission)
    emit(Period.ZERO)

Then we should be able to remove these lines:

   // Always emit at start of flow.
    emit(Period(DateTime.now(), expiry))

    // Delay until the next update interval.
    delay(expiry.millisFromNow() % currentUpdateInterval)

    var delayCount = calculateDelaysNeeded(expiry.millisFromNow(), currentUpdateInterval)

whilst making the code more readable.

Discussed offline. 👏


android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTickerFlow.kt line 68 at r2 (raw file):

Previously, kl (Kalle Lindström) wrote…

Looks like floorDiv can return negative numbers, but we can do this instead:

private fun calculateDelaysNeeded(millisUntilExpiry: Long, currentUpdateIntervalMillis: Long): Long =
    millisUntilExpiry.coerceAtLeast(0) / currentUpdateIntervalMillis

Looks good to me 👍

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @kl and @Pururun)

Rawa
Rawa previously approved these changes Oct 3, 2024
Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @Pururun)

Pururun
Pururun previously approved these changes Oct 3, 2024
Copy link
Contributor

@Pururun Pururun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved

@kl kl dismissed stale reviews from Pururun and Rawa via 298a26c October 4, 2024 10:03
@kl kl force-pushed the ensure-we-update-inappaccountexpiry-notification-droid-1348 branch from 298a26c to 0e2adaa Compare October 4, 2024 10:03
Copy link
Contributor

@Pururun Pururun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just remember to squash commits

Reviewed 7 of 7 files at r4, all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved

@Rawa Rawa force-pushed the ensure-we-update-inappaccountexpiry-notification-droid-1348 branch from 0e2adaa to cd084f8 Compare October 7, 2024 06:34
@Rawa Rawa merged commit efbefb1 into main Oct 7, 2024
29 of 30 checks passed
@Rawa Rawa deleted the ensure-we-update-inappaccountexpiry-notification-droid-1348 branch October 7, 2024 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Android Issues related to Android
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants